/*
 * dfile.c - Linux file processing functions for /proc-based lsof
 */


/*
 * Copyright 1997 Purdue Research Foundation, West Lafayette, Indiana
 * 47907.  All rights reserved.
 *
 * Written by Victor A. Abell
 *
 * This software is not subject to any license of the American Telephone
 * and Telegraph Company or the Regents of the University of California.
 *
 * Permission is granted to anyone to use this software for any purpose on
 * any computer system, and to alter it and redistribute it freely, subject
 * to the following restrictions:
 *
 * 1. Neither the authors nor Purdue University are responsible for any
 *    consequences of the use of this software.
 *
 * 2. The origin of this software must not be misrepresented, either by
 *    explicit claim or by omission.  Credit to the authors and Purdue
 *    University must appear in documentation and sources.
 *
 * 3. Altered versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 4. This notice may not be removed or altered.
 */

#ifndef lint
static char copyright[] =
"@(#) Copyright 1997 Purdue Research Foundation.\nAll rights reserved.\n";
static char *rcsid = "$Id: dfile.c,v 1.8 2012/04/10 16:39:50 abe Exp $";
#endif


#include "lsof.h"


/*
 * Local structures
 */

struct hsfile {
	struct sfile *s;		/* the Sfile table address */
	struct hsfile *next;		/* the next hash bucket entry */
};

/*
 * Local static variables
 */

static struct hsfile *HbyFdi =		/* hash by file (dev,ino) buckets */
	(struct hsfile *)NULL;
static int HbyFdiCt = 0;		/* HbyFdi entry count */
static struct hsfile *HbyFrd =		/* hash by file raw device buckets */
	(struct hsfile *)NULL;
static int HbyFrdCt = 0;		/* HbyFrd entry count */
static struct hsfile *HbyFsd =		/* hash by file system buckets */
	(struct hsfile *)NULL;
static int HbyFsdCt = 0;		/* HbyFsd entry count */
static struct hsfile *HbyNm =		/* hash by name buckets */
	(struct hsfile *)NULL;
static int HbyNmCt = 0;			/* HbyNm entry count */


/*
 * Local definitions
 */

#define	SFDIHASH	4094		/* Sfile hash by (device,inode) number
					 * pair bucket count (power of 2!) */
#define	SFFSHASH	1024		/* Sfile hash by file system device
					 * number bucket count (power of 2!) */
#define SFHASHDEVINO(maj, min, ino, mod) ((int)(((int)((((int)(maj+1))*((int)((min+1))))+ino)*31415)&(mod-1)))
					/* hash for Sfile by major device,
					 * minor device, and inode, modulo mod
					 * (mod must be a power of 2) */
#define	SFRDHASH	1024		/* Sfile hash by raw device number
					 * bucket count (power of 2!) */
#define SFHASHRDEVI(maj, min, rmaj, rmin, ino, mod) ((int)(((int)((((int)(maj+1))*((int)((min+1))))+((int)(rmaj+1)*(int)(rmin+1))+ino)*31415)&(mod-1)))
					/* hash for Sfile by major device,
					 * minor device, major raw device,
					 * minor raw device, and inode, modulo
					 * mod (mod must be a power of 2) */
#define	SFNMHASH	4096		/* Sfile hash by name bucket count
					 * (must be a power of 2!) */


/*
 * hashSfile() - hash Sfile entries for use in is_file_named() searches
 */

void
hashSfile()
{
	static int hs = 0;
	int i;
	struct sfile *s;
	struct hsfile *sh, *sn;
/*
 * Do nothing if there are no file search arguments cached or if the
 * hashes have already been constructed.
 */
	if (!Sfile || hs)
	    return;
/*
 * Allocate hash buckets by (device,inode), file system device, and file name.
 */
	if (!(HbyFdi = (struct hsfile *)calloc((MALLOC_S)SFDIHASH,
					       sizeof(struct hsfile))))
	{
	    (void) fprintf(stderr,
		"%s: can't allocate space for %d (dev,ino) hash buckets\n",
		Pn, SFDIHASH);
	    Exit(1);
	}
	if (!(HbyFrd = (struct hsfile *)calloc((MALLOC_S)SFRDHASH,
					       sizeof(struct hsfile))))
	{
	    (void) fprintf(stderr,
		"%s: can't allocate space for %d rdev hash buckets\n",
		Pn, SFRDHASH);
	    Exit(1);
	}
	if (!(HbyFsd = (struct hsfile *)calloc((MALLOC_S)SFFSHASH,
					       sizeof(struct hsfile))))
	{
	    (void) fprintf(stderr,
		"%s: can't allocate space for %d file sys hash buckets\n",
		Pn, SFFSHASH);
	    Exit(1);
	}
	if (!(HbyNm = (struct hsfile *)calloc((MALLOC_S)SFNMHASH,
					      sizeof(struct hsfile))))
	{
	    (void) fprintf(stderr,
		"%s: can't allocate space for %d name hash buckets\n",
		Pn, SFNMHASH);
	    Exit(1);
	}
	hs++;
/*
 * Scan the Sfile chain, building file, file system, raw device, and file
 * name hash bucket chains.
 */
	for (s = Sfile; s; s = s->next) {
	    for (i = 0; i < 3; i++) {
		switch (i) {
		case 0:			/* hash by name */
		    if (!s->aname)
			continue;
		    sh = &HbyNm[hashbyname(s->aname, SFNMHASH)];
		    HbyNmCt++;
		    break;
		case 1:			/* hash by device and inode, or file
					 * system device */
		    if (s->type) {
			sh = &HbyFdi[SFHASHDEVINO(GET_MAJ_DEV(s->dev),
						  GET_MIN_DEV(s->dev), s->i,
						  SFDIHASH)];
			HbyFdiCt++;
		    } else {
			sh = &HbyFsd[SFHASHDEVINO(GET_MAJ_DEV(s->dev),
						  GET_MIN_DEV(s->dev),
						  0,
						  SFFSHASH)];
			HbyFsdCt++;
		    }
		    break;
		case 2:			/* hash by file's raw device */
		    if ((s->mode == S_IFCHR) || (s->mode == S_IFBLK)) {
			sh = &HbyFrd[SFHASHRDEVI(GET_MAJ_DEV(s->dev),
						 GET_MIN_DEV(s->dev),
						 GET_MAJ_DEV(s->rdev),
						 GET_MIN_DEV(s->rdev),
						 s->i,
						 SFRDHASH)];
			HbyFrdCt++;
		    } else
			continue;
		}
	    /*
	     * Add hash to the bucket's chain, allocating new entries for
	     * all after the first.
	     */
		if (!sh->s) {
		    sh->s = s;
		    sh->next = (struct hsfile *)NULL;
		    continue;
		} else {
		    if (!(sn = (struct hsfile *)malloc(
				(MALLOC_S)sizeof(struct hsfile))))
		    {
			(void) fprintf(stderr,
			    "%s: can't allocate hsfile bucket for: %s\n",
			    Pn, s->aname);
			Exit(1);
		    }
		    sn->s = s;
		    sn->next = sh->next;
		    sh->next = sn;
		}
	    }
	}
}


/*
 * is_file_named() - is this file named?
 */

int
is_file_named(ty, p, mp, cd)
	int ty;				/* search type: 0 = only by device
					 *		    and inode
					 *		1 = by device and
					 *		    inode, or by file
					 *		    system device and
					 *		    path for NFS file
					 *		    systems
					 *		2 = only by path
					 */
	char *p;			/* path name (device and inode are
					 * identified via *Lf) */
	struct mounts *mp;		/* NFS file system (NULL if not) */
	int cd;				/* character or block type file --
					 * VCHR or VBLK vnode, or S_IFCHR
					 * or S_IFBLK inode */
{
	char *ep;
	int f = 0;
	struct mounts *smp;
	struct sfile *s = (struct sfile *)NULL;
	struct hsfile *sh;
	size_t sz;
/*
 * Check for a path name match, as requested.
 */
	if ((ty == 2) && p && HbyNmCt) {
	    for (sh = &HbyNm[hashbyname(p, SFNMHASH)]; sh; sh = sh->next) {
		if ((s = sh->s) && strcmp(p, s->aname) == 0) {
		    f = 2;
		    break;
		}
	    }
	}
/*
 * Check for a regular file by device and inode number.
 */
	if (!f && (ty < 2) && HbyFdiCt && Lf->dev_def
	&& (Lf->inp_ty == 1 || Lf->inp_ty == 3))
	{
	    for (sh = &HbyFdi[SFHASHDEVINO(GET_MAJ_DEV(Lf->dev),
					   GET_MIN_DEV(Lf->dev),
					   Lf->inode,
					   SFDIHASH)];
		 sh;
		 sh = sh->next)
	    {
		if ((s = sh->s) && (Lf->dev == s->dev)
		&&  (Lf->inode == s->i)) {
		    f = 1;
		    break;
		}
	    }
	}
/*
 * Check for a file system match.
 */
	if (!f && (ty == 1) && HbyFsdCt && Lf->dev_def) {
	    for (sh = &HbyFsd[SFHASHDEVINO(GET_MAJ_DEV(Lf->dev),
	    				   GET_MIN_DEV(Lf->dev), 0,
					   SFFSHASH)];
		 sh;
		 sh = sh->next)
	    {
		if ((s = sh->s) && (s->dev == Lf->dev)) {
		    if (Lf->ntype != N_NFS) {

		    /*
		     * A non-NFS file matches to a non-NFS file system by
		     * device.
		     */
			if (!(smp = s->mp) || (smp->ty != N_NFS)) {
			    f = 1;
			    break;
			}
		    } else {

		    /*
		     * An NFS file must also match to a file system by the
		     * the path name of the file system -- i.e., the first
		     * part of the file's path.  This terrible, non-UNIX
		     * hack is forced on lsof by an egregious error in
		     * Linux NFS that can assign the same device number
		     * to two different NFS mounts.
		     */
			if (p && mp && mp->dirl && mp->dir && s->name
			&&  !strncmp(mp->dir, s->name, mp->dirl))
			{
			    f = 1;
			    break;
		 	}
		    }
		}
	    }
	}
/*
 * Check for a character or block device match.
 */
	if (!f && !ty && HbyFrdCt && cd
	&&  Lf->dev_def && (Lf->dev == DevDev)
	&&  Lf->rdev_def
	&& (Lf->inp_ty == 1 || Lf->inp_ty == 3))
	{
	    for (sh = &HbyFrd[SFHASHRDEVI(GET_MAJ_DEV(Lf->dev),
					  GET_MIN_DEV(Lf->dev),
					  GET_MAJ_DEV(Lf->rdev),
					  GET_MIN_DEV(Lf->rdev),
					  Lf->inode, SFRDHASH)];
		 sh;
		 sh = sh->next)
	    {
		if ((s = sh->s) && (s->dev == Lf->dev)
		&&  (s->rdev == Lf->rdev) && (s->i == Lf->inode))
		{
		    f = 1;
		    break;
		}
	    }
	}
/*
 * Convert the name if a match occurred.
 */
	switch (f) {
	case 0:
	    return(0);
	case 1:
	    if (s->type) {

	    /*
	     * If the search argument isn't a file system, propagate it
	     * to Namech[]; otherwise, let printname() compose the name.
	     */
		(void) snpf(Namech, Namechl, "%s", s->name);
		if (s->devnm) {
		    ep = endnm(&sz);
		    (void) snpf(ep, sz, " (%s)", s->devnm);
		}
	    }
	    break;
	case 2:
	    (void) strcpy(Namech, p);
	    break;
	}
	if (s)
	    s->f = 1;
	return(1);
}


/*
 * printdevname() - print character device name
 *
 * Note: this function should not be needed in /proc-based lsof, but
 *	 since it is called by printname() in print.c, an ersatz one
 *	 is provided here.
 */

int
printdevname(dev, rdev, f, nty)
	dev_t *dev;			/* device */
        dev_t *rdev;                    /* raw device */
        int f;                          /* 1 = follow with '\n' */
	int nty;			/* node type: N_BLK or N_chr */
{
	char buf[128];

	(void) snpf(buf, sizeof(buf), "%s device: %d,%d",
		    (nty == N_BLK) ? "BLK" : "CHR",
		    (int)GET_MAJ_DEV(*rdev), (int)GET_MIN_DEV(*rdev));
	safestrprt(buf, stdout, f);
	return(1);
}
